/*
 * Copyright (c) 2009, Hamish Morgan. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the University of Sussex nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package locusts.server.admin;

import javax.swing.table.AbstractTableModel;
import locusts.common.Player;
import locusts.common.entities.Entity;
import locusts.common.entities.EntityList;
import locusts.common.entities.EntityType;
import locusts.common.entities.EntityTypeList;
import locusts.server.Game;
import locusts.server.GameListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A model that allows a JTable to display a list of entities and their
 * properties.
 * 
 * @author Hamish Morgan
 */
public class EntitiesTableModel extends AbstractTableModel {

    private static final Logger LOG = LoggerFactory.getLogger(
            EntitiesTableModel.class);
    //
    private final Column typeColumn = new TypeColumn();
    private final Column idColumn = new IdColumn();
    private final Column xColumn = new XColumn();
    private final Column yColumn = new YColumn();
    private final Column orientationColumn = new OrientationColumn();
    private final Column zColumn = new ZColumn();
    private final Column energyColumn = new EnergyColumn();
    private final Column aliveColumn = new AliveColumn();
    private final Column[] columnOrder = new Column[]{typeColumn, idColumn,
        xColumn, yColumn, orientationColumn, zColumn, energyColumn,
        aliveColumn};
    private final EntityList entities;
    private final EntityTypeList types;
    //
    private long lastUpdate = 0;
    private long updateRate = 1000;

    public EntitiesTableModel(Game game) {
        this.types = game.getEntityTypes();
        this.entities = game.getEntities();
        game.addGameListener(new TMGameListener());
    }

    private class TMGameListener implements GameListener {

        public void gameUpdated(Game g) {

            if (System.currentTimeMillis() > lastUpdate + updateRate) {
                fireTableRowsUpdated(0, getRowCount());
                lastUpdate = System.currentTimeMillis();
            }
        }

        public void playerJoined(Game game, Player player) {
//                throw new UnsupportedOperationException("Not supported yet.");
            }

        public void playerLeft(Game game, Player player) {
//                throw new UnsupportedOperationException("Not supported yet.");
            }

        public void gameEntitiesAdded(Game game) {
            fireTableDataChanged();
        }

        public void gameEntitiesRemoved(Game game) {
            fireTableDataChanged();
        }
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return columnOrder[columnIndex].isEditable();
    }

    public int getRowCount() {
        return entities.size();
    }

    public int getColumnCount() {
        return columnOrder.length;
    }

    public Object getValueAt(int rowIndex, int columnIndex) {
        try {
            return columnOrder[columnIndex].getValue(entities.get(rowIndex));
        } catch (java.lang.IndexOutOfBoundsException e) {
            // this is a concurrency issue and will self correct 
            return null;
        }
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        boolean updated = columnOrder[columnIndex].setValue(entities.get(
                rowIndex), aValue);
        if (updated) {
            fireTableCellUpdated(rowIndex, columnIndex);
        }
    }

    @Override
    public String getColumnName(int column) {
        return columnOrder[column].getName();
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return columnOrder[columnIndex].getValueClass();
    }

    protected abstract static class Column {

        private String name;
        protected Class<?> c;

        public Column(Class<?> c, String name) {
            this.name = name;
            this.c = c;
        }

        public String getName() {
            return name;
        }

        public boolean isEditable() {
            return true;
        }

        public Class<?> getValueClass() {
            return c;
        }

        public abstract Object getValue(Entity e);

        public abstract boolean setValue(Entity e, Object value);
    }

    protected class TypeColumn extends Column {

        public TypeColumn() {
            super(EntityType.class, "Type");
        }

        @Override
        public Object getValue(Entity e) {
            return types.getId(e.getTypeId());//.getName();
        }

        public boolean setValue(Entity e, Object value) {

            final int newTypeId = ((EntityType)value).getId();
            if (e.getTypeId() != newTypeId) {
                e.setTypeId(newTypeId);
                e.setModified();
                return true;
            } else {
                return false;
            }
        }
    }

    protected class IdColumn extends Column {

        public IdColumn() {
            super(Integer.class, "Id");
        }

        @Override
        public Object getValue(Entity e) {
            return e.getId();
        }

        public boolean setValue(Entity e, Object value) {
            return false;
        }

        @Override
        public boolean isEditable() {
            return false;
        }
    }

    protected class XColumn extends Column {

        public XColumn() {
            super(Double.class, "X");
        }

        @Override
        public Object getValue(Entity e) {
            return e.getX();
        }

        public boolean setValue(Entity e, Object value) {
            final double newX = (Double) value;
            if (newX != e.getX()) {
                e.setX(newX);
                e.setModified();
                return true;
            } else {
                return false;
            }

        }
    }

    protected class YColumn extends Column {

        public YColumn() {
            super(Double.class, "Y");
        }

        @Override
        public Object getValue(Entity e) {
            return new Double(e.getY());
        }

        public boolean setValue(Entity e, Object value) {
            final double newY = (Double) value;
            if (newY != e.getY()) {
                e.setY(newY);
                e.setModified();
                return true;
            } else {
                return false;
            }
        }
    }

    protected class ZColumn extends Column {

        public ZColumn() {
            super(Integer.class, "Z");
        }

        @Override
        public Object getValue(Entity e) {
            return new Integer(e.getzOrder());
        }

        public boolean setValue(Entity e, Object value) {
            final int newZ = (Integer) value;
            if (newZ != e.getzOrder()) {
                e.setzOrder(newZ);
                e.setModified();
                return true;
            } else {
                return false;
            }
        }
    }

    protected class EnergyColumn extends Column {

        public EnergyColumn() {
            super(Double.class, "Energy");
        }

        @Override
        public Object getValue(Entity e) {
            return new Double(e.getEnergy());
        }

        public boolean setValue(Entity e, Object value) {
            final double newEnergy = (Double) value;
            if (newEnergy != e.getEnergy()) {
                e.setEnergy(newEnergy);
                e.setModified();
                return true;
            } else {
                return false;
            }
        }
    }

    protected class OrientationColumn extends Column {

        public OrientationColumn() {
            super(Double.class, "Orientation");
        }

        @Override
        public Object getValue(Entity e) {
            return new Double(e.getOrientation());
        }

        public boolean setValue(Entity e, Object value) {
            final double newOr = (Double) value;
            if (newOr != e.getOrientation()) {
                e.setOrientation(newOr);
                e.setModified();
                return true;
            } else {
                return false;
            }
        }
    }

    protected class AliveColumn extends Column {

        public AliveColumn() {
            super(Boolean.class, "Alive");
        }

        @Override
        public Object getValue(Entity e) {
            return new Boolean(e.isAlive());
        }

        public boolean setValue(Entity e, Object value) {
            final boolean newAlive = (Boolean) value;
            if (newAlive != e.isAlive()) {
                e.setAlive(newAlive);
                e.setModified();
                return true;
            } else {
                return false;
            }
        }
    }
}
